package zip

import (
	"archive/zip"
	"bytes"
	"errors"
	"fmt"
	"golang.org/x/text/encoding/simplifiedchinese"
	"golang.org/x/text/transform"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
)

var (
	ErrNotFound = errors.New("not found")
)

type Zip interface {
	Compress([]string, string, bool, string) error
	CompressSpecifyPositionFiles(files map[string]string, savePath string, isRecursive bool) error
	UnCompress(string, string, bool) error
	ReadFile(string, string) ([]byte, error)
}

type z struct{}

func New() Zip {
	c := &z{}
	return c
}

/**
 * 压缩文件(递归)
 * files 文件路径数组，可以是不同dir下的文件或者文件夹
 * saveFilePath 压缩文件存放路径（含文件名）
 * filterPrefix 过滤压缩包中文件路径前缀
 */
func (c *z) Compress(files []string, saveFilePath string, isRecursive bool, filterPrefix string) error {
	filterPrefix = filepath.Clean(filterPrefix)
	saveFilePath = filepath.Clean(saveFilePath)
	saveFilePath, err := filepath.Abs(saveFilePath)
	if err != nil {
		return err
	}
	dirname := filepath.Dir(saveFilePath)
	if _, err := os.Stat(dirname); err != nil {
		if os.IsNotExist(err) {
			if err := os.MkdirAll(dirname, 0755); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	d, err := os.Create(saveFilePath)
	if err != nil {
		return err
	}
	defer d.Close()

	writer := zip.NewWriter(d)
	defer writer.Close()
	for _, filePath := range files {
		filePath = filepath.Clean(filePath)
		f, err := os.Open(filePath)
		if err != nil {
			return err
		}

		var pkgPath = strings.Replace(filePath, filterPrefix, "", 1)
		//pkgPath = strings.Replace(pkgPath, string(os.PathSeparator), "", 1)

		fmt.Println(pkgPath)

		var prefixDirs []string
		if pkgPath != "" {
			prefixDirs = strings.Split(filepath.Dir(pkgPath), string(os.PathSeparator))
		}

		if isRecursive {
			if err := c.compressRecursive(writer, f, prefixDirs); err != nil {
				return err
			}
		} else {
			if err := c.compress(writer, f, prefixDirs); err != nil {
				return err
			}
		}
	}
	return nil
}

//按路径压缩文件
func (c *z) CompressSpecifyPositionFiles(files map[string]string, savePath string, isRecursive bool) error {
	savePath, err := filepath.Abs(savePath)
	if err != nil {
		return err
	}
	dirname := filepath.Dir(savePath)
	if _, err := os.Stat(dirname); err != nil {
		if os.IsNotExist(err) {
			if err := os.MkdirAll(dirname, 0755); err != nil {
				return err
			}
		} else {
			return err
		}
	}

	d, err := os.Create(savePath)
	if err != nil {
		return err
	}
	defer d.Close()
	writer := zip.NewWriter(d)
	defer writer.Close()
	for filePath, pkgPath := range files {
		f, err := os.Open(filePath)
		if err != nil {
			return err
		}
		var prefixDirs = strings.Split(pkgPath, string(os.PathSeparator))
		if isRecursive {
			if err := c.compressRecursive(writer, f, prefixDirs); err != nil {
				return err
			}
		} else {
			if err := c.compress(writer, f, prefixDirs); err != nil {
				return err
			}
		}
	}
	return nil
}

func (c *z) compress(zipWriter *zip.Writer, file *os.File, prefix []string) error {
	defer file.Close()
	info, err := file.Stat()
	if err != nil {
		return err
	}
	header, err := zip.FileInfoHeader(info)
	if err != nil {
		return err
	}
	prefix = append(prefix, header.Name)
	header.Name = filepath.Join(prefix...)
	if info.IsDir() {
		header.Name += string(os.PathSeparator)
	}
	writer, err := zipWriter.CreateHeader(header)
	if err != nil {
		return err
	}
	if !info.IsDir() {
		if _, err = io.Copy(writer, file); err != nil {
			return err
		}
	}
	return nil
}

func (c *z) compressRecursive(zipWriter *zip.Writer, file *os.File, prefix []string) error {
	defer file.Close()
	info, err := file.Stat()
	if err != nil {
		return err
	}
	if info.IsDir() {
		fileInfos, err := file.Readdir(-1)
		if err != nil {
			return err
		}
		prefix = append(prefix, info.Name())
		if len(fileInfos) > 0 {
			for _, fi := range fileInfos {
				f, err := os.Open(filepath.Join(file.Name(), fi.Name()))
				if err != nil {
					return err
				}
				if err = c.compressRecursive(zipWriter, f, prefix); err != nil {
					return err
				}
			}
		} else {
			header, err := zip.FileInfoHeader(info)
			if err != nil {
				return err
			}
			header.Name = filepath.Join(prefix...) + string(os.PathSeparator)
			if _, err = zipWriter.CreateHeader(header); err != nil {
				return err
			}
		}
	} else {
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}
		prefix = append(prefix, header.Name)
		header.Name = filepath.Join(prefix...)
		writer, err := zipWriter.CreateHeader(header)
		if err != nil {
			return err
		}
		if _, err = io.Copy(writer, file); err != nil {
			return err
		}
	}
	return nil
}

//解压
func (c *z) UnCompress(zipPath, dstPath string, overwrite bool) error {
	reader, err := zip.OpenReader(zipPath)
	if err != nil {
		return err
	}
	defer reader.Close()
	for _, file := range reader.File {
		var decodeName string
		if file.Flags == 0 {
			//如果标致位是0  则是默认的本地编码默认为gbk
			i := bytes.NewReader([]byte(file.Name))
			decoder := transform.NewReader(i, simplifiedchinese.GB18030.NewDecoder())
			content, _ := ioutil.ReadAll(decoder)
			decodeName = string(content)
		} else if file.Flags == 8 {
			i := bytes.NewReader([]byte(file.Name))
			decoder := transform.NewReader(i, simplifiedchinese.GB18030.NewDecoder())
			content, _ := ioutil.ReadAll(decoder)
			decodeName = string(content)
		} else {
			//如果标志为是 1 << 11也就是 2048  则是utf-8编码
			decodeName = file.Name
		}
		filename := filepath.Join(dstPath, decodeName)
		if file.FileInfo().IsDir() {
			if err := os.MkdirAll(filename, os.ModePerm); err != nil {
				return err
			}
		} else {
			dirname := filepath.Dir(filename)
			if err = os.MkdirAll(dirname, 0755); err != nil {
				return err
			}
			rc, err := file.Open()
			if err != nil {
				return err
			}
			var writer *os.File
			var writerErr error
			if overwrite {
				writer, writerErr = os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755)
				if writerErr != nil {
					return writerErr
				}
			} else {
				writer, writerErr = os.Create(filename)
				if writerErr != nil {
					return writerErr
				}
			}

			if _, err = io.Copy(writer, rc); err != nil {
				return err
			}
			if err := writer.Close(); err != nil {
				return err
			}
			if err := rc.Close(); err != nil {
				return err
			}
		}
	}
	return nil
}

//获取zip中文件内容
func (c *z) ReadFile(zipPath string, pkgPath string) ([]byte, error) {
	pkgPath = c.formatCompressPath(pkgPath)
	reader, err := zip.OpenReader(zipPath)
	if err != nil {
		return nil, err
	}
	defer reader.Close()

	var file *zip.File
	for _, f := range reader.File {
		var decodeName string
		if f.Flags == 0 {
			//如果标致位是0  则是默认的本地编码   默认为gbk
			i := bytes.NewReader([]byte(f.Name))
			decoder := transform.NewReader(i, simplifiedchinese.GB18030.NewDecoder())
			content, _ := ioutil.ReadAll(decoder)
			decodeName = string(content)
		} else if f.Flags == 8 {
			i := bytes.NewReader([]byte(f.Name))
			decoder := transform.NewReader(i, simplifiedchinese.GB18030.NewDecoder())
			content, _ := ioutil.ReadAll(decoder)
			decodeName = string(content)
		} else {
			//如果标志为是 1 << 11也就是 2048  则是utf-8编码
			decodeName = f.Name
		}
		if decodeName == pkgPath {
			file = f
			break
		}
	}
	if file == nil {
		return nil, ErrNotFound
	}
	fp, e := file.Open()
	if e != nil {
		return nil, e
	}
	defer fp.Close()

	var fileBytes []byte
	var buf = make([]byte, 1024)
	for {
		n, e := fp.Read(buf)
		if e != nil && e != io.EOF {
			break
		}
		if n == 0 {
			break
		}
		fileBytes = append(fileBytes, buf[:n]...)
	}
	return fileBytes, nil
}

//判断切片中是否含有某个字符串
func (c *z) inStringSlice(str string, strings []string) bool {
	for _, v := range strings {
		if v == str {
			return true
		}
	}
	return false
}

// 格式化压缩包文件路径
func (c *z) formatCompressPath(p string) string {
	if p == "" {
		return ""
	}
	var res string
	var pl = filepath.SplitList(p)
	if len(pl) == 2 { //windows
		pl[1] = ClearFirstSplitter(pl[1])
		res = strings.Join(pl, "/")
	} else { //unix
		pl[0] = ClearFirstSplitter(pl[0])
		res = pl[0]
	}
	dir, file := SplitPath(res)
	dir = strings.ReplaceAll(dir, ".", "")
	return dir + file
}

// 清理路径开头的路径分隔符
func ClearFirstSplitter(s string) string {
	str := []rune(s)
	s1 := string(str[0])
	if IsPathSplitter(s1) {
		return string(str[1:])
	}
	return s
}

// 判断字符串是否为路径分隔符
func IsPathSplitter(s string) bool {
	return s == `/` || s == `\`
}

// 拆分文件路径为目录和文件
func SplitPath(p string) (dir string, file string) {
	str := []rune(p)
	strLen := len(str)
	for i := strLen - 1; i >= 0; i-- {
		if IsPathSplitter(string(str[i])) {
			return string(str[:i+1]), string(str[i+1:])
		}
	}
	return "", p
}